﻿using System;
using UnityEngine;
using UnityEngine.UIElements;
using winS.Unity;
using Math = winS.Unity.Math;

namespace winS.UnityEditor.ProcessEditor
{
    /// <summary>
    /// 为 <see cref="StepGraphView"/> 提供框选相关功能的支持
    /// </summary>
    public class Selector : Manipulator
    {
        public event Action onBoxSelectEnd;
        public event Action onBoxSelectStart;

        private bool isEnabled;

        private Vector2 lastLocalMousePosition;

        private readonly RectangleElement rectangleElement;

        public Selector(StepGraphView stepGraphView, ProcessEditorData processEditorData) : base(stepGraphView)
        {
            rectangleElement = new RectangleElement(processEditorData.selectionSetting);

            stepGraphView.RegisterCallback<MouseUpEvent>(MouseUpCallback);
            stepGraphView.RegisterCallback<MouseDownEvent>(MouseDownCallback);
            stepGraphView.RegisterCallback<MouseMoveEvent>(MouseMoveCallback);
        }

        public void PanRectangle(Vector2 offset)
        {
            if (!isEnabled) return;
            rectangleElement.Update(stepGraphView.ChangeCoordinatesTo(contentViewContainer, lastLocalMousePosition));
        }

        private void MouseUpCallback(MouseUpEvent mouseUpEvent)
        {
            if (!isEnabled) return;
            isEnabled = false;
            stepGraphView.ReleaseMouse();
            mouseUpEvent.StopPropagation();

            rectangleElement.End();
            contentViewContainer.Remove(rectangleElement);
            Vector2 startPointInContent = rectangleElement.startPoint;
            Vector2 endPointInContent = stepGraphView.ChangeCoordinatesTo(contentViewContainer, mouseUpEvent.localMousePosition);

            float minX = Math.Min(startPointInContent.x, endPointInContent.x);
            float maxX = Math.Max(startPointInContent.x, endPointInContent.x);
            float minY = Math.Min(startPointInContent.y, endPointInContent.y);
            float maxY = Math.Max(startPointInContent.y, endPointInContent.y);

            Rect selectionRect = Rect.MinMaxRect(minX, minY, maxX, maxY);
            foreach (var graphElement in stepGraphView.graphElements)
            {
                if (graphElement.selected || !graphElement.selectable) continue;
                if (!graphElement.layout.Overlaps(selectionRect)) continue;
                stepGraphView.AddToSelection(graphElement);
            }
            onBoxSelectEnd?.Invoke();
        }
        private void MouseDownCallback(MouseDownEvent mouseDownEvent)
        {
            if (mouseDownEvent.target != stepGraphView) return;
            if (mouseDownEvent.button != 0) return;
            isEnabled = true;
            if (mouseDownEvent.modifiers != EventModifiers.Control) stepGraphView.ClearSelection();
            stepGraphView.CaptureMouse();
            onBoxSelectStart?.Invoke();
            contentViewContainer.Add(rectangleElement);
            rectangleElement.Start(stepGraphView.ChangeCoordinatesTo(contentViewContainer, lastLocalMousePosition = mouseDownEvent.localMousePosition));
        }
        private void MouseMoveCallback(MouseMoveEvent mouseMoveEvent)
        {
            if (!isEnabled) return;
            mouseMoveEvent.StopPropagation();
            rectangleElement.Update(stepGraphView.ChangeCoordinatesTo(contentViewContainer, lastLocalMousePosition = mouseMoveEvent.localMousePosition));
        }

        private class RectangleElement : VisualElement
        {
            public static readonly float segmentsWidth = 4f;
            public static readonly float segmentsLength = 5.4f;

            public Vector2 startPoint { get; private set; }

            private Color color;
            private Vector2 endPoint;

            public RectangleElement(ProcessEditorData.SelectionSetting selectionSetting)
            {
                color = selectionSetting.selectBoxColor;
                selectionSetting.selectBoxColor.onValueChanged += newColor =>
                {
                    color = newColor;
                    MarkDirtyRepaint();
                };

                pickingMode = PickingMode.Ignore;
                style.position = Position.Absolute;
                generateVisualContent += OnGenerateVisualContent;
            }

            public void Start(Vector2 startPoint)
            {
                this.startPoint = startPoint;
            }
            public void End()
            {
                style.width = style.height = 0f;
            }
            public void Update(Vector2 endPoint)
            {
                this.endPoint = endPoint;
                UpdateRect();
                MarkDirtyRepaint();
            }

            private void UpdateRect()
            {
                Math.MinMax(startPoint.x, endPoint.x, out float minX, out float maxX);
                Math.MinMax(startPoint.y, endPoint.y, out float minY, out float maxY);

                Rect rect = Rect.MinMaxRect(minX, minY, maxX, maxY);

                style.width = rect.width;
                style.height = rect.height;
                style.top = rect.yMin;
                style.left = rect.xMin;
            }
            private void OnGenerateVisualContent(MeshGenerationContext meshGenerationContext)
            {
                float width = style.width.value.value;
                float height = style.height.value.value;

                Painter2D painter2D = meshGenerationContext.painter2D;
                painter2D.strokeColor = color;
                painter2D.lineWidth = segmentsWidth;
                painter2D.BeginPath();
                DrawDottedline(new Vector2(0f, 0f), new Vector2(width, 0f), painter2D);//上侧虚线
                DrawDottedline(new Vector2(0f, 0f), new Vector2(0f, height), painter2D);//左侧虚线
                DrawDottedline(new Vector2(width, 0f), new Vector2(width, height), painter2D);//右侧虚线
                DrawDottedline(new Vector2(0f, height), new Vector2(width, height), painter2D);//下侧虚线

                painter2D.ClosePath();
                painter2D.Stroke();
            }


            private void DrawDottedline(Vector2 startPoint, Vector2 endPoint, Painter2D painter2D)
            {
                Vector2 vector = endPoint - startPoint;
                Vector2 direction = vector.normalized;
                float distance = vector.magnitude;
                float currentDistance = 0f;

                while (true)
                {
                    if (currentDistance >= distance) break;
                    float lineLength = Math.Min(distance - currentDistance, segmentsLength);
                    currentDistance += lineLength;
                    Vector2 currentEndPoint = startPoint + direction * lineLength;
                    painter2D.MoveTo(startPoint);
                    painter2D.LineTo(currentEndPoint);
                    startPoint = currentEndPoint;
                    if (currentDistance >= distance) break;
                    float spaceLength = Math.Min(distance - currentDistance, segmentsLength);
                    currentDistance += spaceLength;
                    startPoint += direction * spaceLength;
                }
            }
        }

        private class Rectangle : ImmediateModeElement
        {
            public static readonly float segmentsWidth = 3.6f;
            public static readonly float segmentsLength = 5.4f;

            public Color color = new Color(1f, 0.6f, 0f, 1f);
            public Vector2 endPoint;
            public Vector2 startPoint;

            private readonly Material lineMaterial;
            private readonly StepGraphView stepGraphView;

            public Rectangle(StepGraphView stepGraphView)
            {
                this.stepGraphView = stepGraphView;
                lineMaterial = new Material(Shader.Find("Hidden/Internal-Colored"));
                lineMaterial.hideFlags = HideFlags.HideAndDontSave;
            }

            protected override void ImmediateRepaint()
            {
                float minX = Math.Min(startPoint.x, endPoint.x);
                float maxX = Math.Max(startPoint.x, endPoint.x);
                float minY = Math.Min(startPoint.y, endPoint.y);
                float maxY = Math.Max(startPoint.y, endPoint.y);

                Vector3 leftTop = new Vector3(minX, minY);
                Vector3 rightTop = new Vector3(maxX, minY);
                Vector3 leftBottom = new Vector3(minX, maxY);
                Vector3 rightBottom = new Vector3(maxX, maxY);

                lineMaterial.SetPass(0);

                GL.Begin(GL.QUADS);
                GL.Color(color);
                DrawDottedline(leftTop, rightTop);
                DrawDottedline(rightTop, rightBottom);
                DrawDottedline(leftBottom, rightBottom);
                DrawDottedline(leftTop, leftBottom);
                GL.End();
            }

            private void DrawDottedline(Vector3 startPoint, Vector3 endPoint)
            {
                Vector3 vector = endPoint - startPoint;
                Vector3 direction = vector.normalized;
                Vector3 rightDirection = VectorUtility.VerticalDirectionRight(direction, true);
                float distance = vector.magnitude;
                float currentDistance = 0f;
                float finalSegmentsWidth = segmentsWidth / stepGraphView.currentScale;
                float finalSegmentsLength = segmentsLength / stepGraphView.currentScale;

                while (true)
                {
                    if (currentDistance >= distance) break;
                    float lineLength = Math.Min(distance - currentDistance, finalSegmentsLength);
                    currentDistance += lineLength;
                    Vector3 currentEndPoint = startPoint + direction * lineLength;
                    Vector3 rightDelta = rightDirection * finalSegmentsWidth;
                    DrawRect(startPoint, currentEndPoint, startPoint + rightDelta, currentEndPoint + rightDelta);
                    startPoint = currentEndPoint;
                    if (currentDistance >= distance) break;
                    float spaceLength = Math.Min(distance - currentDistance, finalSegmentsLength);
                    currentDistance += spaceLength;
                    startPoint += direction * spaceLength;
                }
            }

            private void DrawRect(Vector3 leftTop, Vector3 rightTop, Vector3 leftBottom, Vector3 rightBottom)
            {
                GL.Vertex(leftTop);
                GL.Vertex(rightTop);
                GL.Vertex(rightBottom);
                GL.Vertex(leftBottom);
            }
        }
    }
}